//
// Created by Yun Zeng on 2018/4/14.
// 光照强度传感器 TSL2560
//

#include "platform_light.h"
#include "master.h"

#include <linux/i2c.h>
#include <linux/delay.h>

#include "platform_light_reg.h"

// I2C 驱动设备信息
//struct i2c_adapter* tsl2561_dev = NULL;
//struct i2c_client* tsl2561_client = NULL;

static struct i2c_board_info __initdata board_info =  {
        .type = "tsl2561", .addr = TSL2561_ADDR_FLOAT,
};

// TSL2560 设备相关
typedef struct tsl2561InfoType {

    struct i2c_adapter* dev;
    struct i2c_client* client;

    tsl2561GainType gain;
    tsl2561IntegrationTimeType integration;
    uint8_t buffer[10];

    light_recv_callback callback;
}tsl2561InfoType;

static tsl2561InfoType tsl2561Info;


static inline int tsl2561_get_data(uint16_t *full_spectrum, uint16_t *infrared) {
    i2c_smbus_write_byte_data(tsl2561Info.client, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWERON);

    switch (tsl2561Info.integration) {
        case TSL2561_INTEGRATIONTIME_13MS:
            msleep(20);
            break;
        case TSL2561_INTEGRATIONTIME_101MS:
            msleep(150);
            break;
        case TSL2561_INTEGRATIONTIME_402MS:
            msleep(450);
            break;
    }

    *full_spectrum = (uint16_t) i2c_smbus_read_word_data(tsl2561Info.client, TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN0_LOW);
    *infrared = (uint16_t) i2c_smbus_read_word_data(tsl2561Info.client, TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN1_LOW);

    i2c_smbus_write_byte_data(tsl2561Info.client, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWEROFF);
    return 0;
}

// 根据测量的数值计算可见光强度 lux
static uint32_t tsl2561_calculate_lux(uint16_t broadband, uint16_t ir) {
    unsigned long chScale;
    unsigned long channel1;
    unsigned long channel0;
    uint16_t clipThreshold;
    unsigned long ratio1 = 0;
    unsigned long ratio;
    unsigned int b, m;
    long temp;
    uint32_t lux;

    // Make sure the sensor isn't saturated!
    switch (tsl2561Info.integration) {
        case TSL2561_INTEGRATIONTIME_13MS:
            clipThreshold = TSL2561_CLIPPING_13MS;
            break;
        case TSL2561_INTEGRATIONTIME_101MS:
            clipThreshold = TSL2561_CLIPPING_101MS;
            break;
        case TSL2561_INTEGRATIONTIME_402MS:
            clipThreshold = TSL2561_CLIPPING_402MS;
            break;
        default:
            clipThreshold = TSL2561_CLIPPING_402MS;
            break;
    }

    if ((broadband > clipThreshold) || (ir > clipThreshold)) {
        return 0;
    }

    switch (tsl2561Info.integration) {
        case TSL2561_INTEGRATIONTIME_13MS:
            chScale = TSL2561_LUX_CHSCALE_TINT0;
            break;
        case TSL2561_INTEGRATIONTIME_101MS:
            chScale = TSL2561_LUX_CHSCALE_TINT1;
            break;
        case TSL2561_INTEGRATIONTIME_402MS:
            chScale = (1 << TSL2561_LUX_CHSCALE);
            break;
        default:
            chScale = (1 << TSL2561_LUX_CHSCALE);
            break;
    }

    if (!tsl2561Info.gain) chScale = chScale << 4;

    channel0 = (broadband * chScale) >> TSL2561_LUX_CHSCALE;
    channel1 = (ir * chScale) >> TSL2561_LUX_CHSCALE;

    // find the ratio of the channel values (Channel1/Channel0)
    if (channel0 != 0) ratio1 = (channel1 << (TSL2561_LUX_RATIOSCALE + 1)) / channel0;

    // round the ratio value
    ratio = (ratio1 + 1) >> 1;

    if ((ratio >= 0) && (ratio <= TSL2561_LUX_K1T)) {
        b = TSL2561_LUX_B1T;
        m = TSL2561_LUX_M1T;
    } else if (ratio <= TSL2561_LUX_K2T) {
        b = TSL2561_LUX_B2T;
        m = TSL2561_LUX_M2T;
    } else if (ratio <= TSL2561_LUX_K3T) {
        b = TSL2561_LUX_B3T;
        m = TSL2561_LUX_M3T;
    } else if (ratio <= TSL2561_LUX_K4T) {
        b = TSL2561_LUX_B4T;
        m = TSL2561_LUX_M4T;
    } else if (ratio <= TSL2561_LUX_K5T) {
        b = TSL2561_LUX_B5T;
        m = TSL2561_LUX_M5T;
    } else if (ratio <= TSL2561_LUX_K6T) {
        b = TSL2561_LUX_B6T;
        m = TSL2561_LUX_M6T;
    } else if (ratio <= TSL2561_LUX_K7T) {
        b = TSL2561_LUX_B7T;
        m = TSL2561_LUX_M7T;
    } else if (ratio > TSL2561_LUX_K8T) {
        b = TSL2561_LUX_B8T;
        m = TSL2561_LUX_M8T;
    }

    temp = ((channel0 * b) - (channel1 * m));
    // do not allow negative lux value
    if (temp < 0) {
        temp = 0;
    }
    temp += (1 << (TSL2561_LUX_LUXSCALE - 1));
    lux = temp >> TSL2561_LUX_LUXSCALE;
    return lux;
}

int tsl2561_set_integration_time(tsl2561IntegrationTimeType time) {
    int rc;
    i2c_smbus_write_byte_data(tsl2561Info.client, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWERON);
    rc = i2c_smbus_write_byte_data(tsl2561Info.client, TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, time | tsl2561Info.gain);
    i2c_smbus_write_byte_data(tsl2561Info.client, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWEROFF);
    if (rc != 0) {
        return -1;
    }
//    printk(MASTER_DEV_NAME ": light integration rc(%d)\n", rc);
    return 0;
}

int tsl2561_set_gain(tsl2561GainType gain) {
    int rc;
    i2c_smbus_write_byte_data(tsl2561Info.client, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWERON);
    rc = i2c_smbus_write_byte_data(tsl2561Info.client, TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, gain | tsl2561Info.integration);
    i2c_smbus_write_byte_data(tsl2561Info.client, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWEROFF);
    if (rc != 0) {
        return -1;
    }
    tsl2561Info.gain = gain;
//    printk(MASTER_DEV_NAME ": light gain rc(%d)\n", rc);
    return 0;
}

int tsl2561_get_light(uint16_t *full_spectrum, uint16_t *infrared, uint32_t *lux, int autogain) {
    int rc=1;
    uint16_t fs, ir, hi, lo;

    if (autogain == 0) {
        rc = tsl2561_get_data(full_spectrum, infrared);
        *lux = tsl2561_calculate_lux(*full_spectrum, *infrared);
        return rc;
    }

    // auto gain requested
    switch(tsl2561Info.integration) {
        case TSL2561_INTEGRATIONTIME_13MS:
            hi = TSL2561_AGC_THI_13MS;
            lo = TSL2561_AGC_TLO_13MS;
            break;
        case TSL2561_INTEGRATIONTIME_101MS:
            hi = TSL2561_AGC_THI_101MS;
            lo = TSL2561_AGC_TLO_101MS;
            break;
        case TSL2561_INTEGRATIONTIME_402MS:
            hi = TSL2561_AGC_THI_402MS;
            lo = TSL2561_AGC_TLO_402MS;
            break;
        default:
            hi = TSL2561_AGC_THI_402MS;
            lo = TSL2561_AGC_TLO_402MS;
            break;
    }

    rc = tsl2561_get_data(&fs, &ir);
    if(rc != 0) {
        return -1;
    }

    // light too low with this gain
    if ((fs < lo) && (tsl2561Info.gain == TSL2561_GAIN_1X)) {
        tsl2561_set_gain(TSL2561_GAIN_16X);
        rc = tsl2561_get_data(&fs, &ir);
        if(rc != 0) {
            return -1;
        } else {
            *full_spectrum = fs;
            *infrared = ir;
            *lux = tsl2561_calculate_lux(*full_spectrum, *infrared);
            return 0;
        }
    }

    // light too high with this gain
    if ((fs > hi) && (tsl2561Info.gain == TSL2561_GAIN_16X)) {
        tsl2561_set_gain(TSL2561_GAIN_1X);
        rc = tsl2561_get_data(&fs, &ir);
        if(rc != 0) {
            return -1;
        } else {
            *full_spectrum = fs;
            *infrared = ir;
            *lux = tsl2561_calculate_lux(*full_spectrum, *infrared);
            return 0;
        }
    }

    *full_spectrum = fs;
    *infrared = ir;
    *lux = tsl2561_calculate_lux(*full_spectrum, *infrared);
    return 0;
}

// 写入指令，读取光照强度传感器的值
int32_t platform_light_write(uint8_t *data, uint16_t len) {
    int rc;
    uint16_t broadband, infrared;
    uint32_t lux;

    tsl2561_set_gain(TSL2561_GAIN_1X);
    tsl2561_set_integration_time(TSL2561_INTEGRATIONTIME_101MS);

    rc = tsl2561_get_light(&broadband, &infrared, &lux, 1);
    //printk(MASTER_DEV_NAME ": tsl2561 broadband(%d) infrared(%d) lux(%d)\n", broadband, infrared, lux);

    if (tsl2561Info.callback != NULL) {
        platform_light_data_typedef light = {
            .lux = cpu_to_be32(lux),
            .infrared = cpu_to_be16(infrared),
        };
        tsl2561Info.callback(&light);
    }

    return 0;
}


int platform_light_init(light_recv_callback callback) {
    tsl2561Info.dev = i2c_get_adapter(0);
    tsl2561Info.client = i2c_new_device(tsl2561Info.dev, &board_info);

    if (tsl2561Info.client == NULL) {
        printk(MASTER_DEV_NAME ": tsl2561 client init() error\n");
        return -1;
    }

    memset(tsl2561Info.buffer, 0x00, sizeof(tsl2561Info.buffer));

    tsl2561_set_integration_time(TSL2561_INTEGRATIONTIME_402MS);
    tsl2561_set_gain(TSL2561_GAIN_16X);

    tsl2561Info.callback = callback;
    return 0;
}

int platform_light_exit(void) {
    if (tsl2561Info.client != NULL) {
        i2c_unregister_device(tsl2561Info.client);
    }
    tsl2561Info.callback = NULL;
    return 0;
}